package com.server.net.message;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.msg.MsgMain;
import com.server.util.LogUtil;
import com.server.util.MsgFunction;
import com.server.util.PackageUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 单例模式，处理协议分发
 */
public class MsgProcessorManager {

    private static final MsgProcessorManager instance = new MsgProcessorManager();

    public static MsgProcessorManager getInstance() {
        return instance;
    }

    //存储协议id对应得方法及对象,不使用final，方便热更
    private Map<Integer, MsgMethod> msg2Function = new ConcurrentHashMap<>();

    private MsgDispatcher loginScene = new MsgDispatcher();//仅处理登陆协议,保证顺序，隔离压力

    private boolean start = false;

    public void init(String msgPackageName) throws Exception {
        Map<Integer, MsgMethod> tempMsg2Function = new ConcurrentHashMap<>();
        final List<String> handlerList = PackageUtil.getClassName(msgPackageName);
        for (String handler : handlerList) {
            Class<?> handlerClass = Class.forName(handler);
            packMsg(handlerClass, tempMsg2Function);
        }
        this.msg2Function = tempMsg2Function;
        if (!this.start) {
            this.start = true;
            loginScene.startUp();
        }
    }

    public void close() {
        LogUtil.game.info("MsgProcessorManager close begin");
        this.start = false;
        loginScene.close();
        LogUtil.game.info("MsgProcessorManager close end");
    }

    public void processMsg(MsgPack msgPack) {
        MsgMethod method = msg2Function.get(msgPack.getMsgIndex());
        if (Objects.isNull(method)) {
            return;
        }
        /*try {
            //传统反射缓存
            method.getMethod().setAccessible(true);//闭安全检查，从而提升反射效率1倍
            method.getMethod().invoke(method.getObj(), msgPack);//缓存method和对象，相对于直接调用，慢约10倍
        } catch (IllegalAccessException | InvocationTargetException e) {
            LogUtil.game.error("Exception", e);
        }*/

        //使用 reflectASM 通过字节码调用invoke,极致优化反射
        //无法针对私有方法、私有属性
        //虽然jdk9+会有警告，但不影响。只要不调用私有方法、私有属性就可以
        //最快的则是预先取得MethodAccess和函数的索引并用索引来调用。其时间消耗仅仅是直接调用的2倍不到。
        method.getMethodAccess().invoke(method.getObj(), method.getMethodIndex(), msgPack);
    }

    private void packMsg(Class<?> clazz, Map<Integer, MsgMethod> msg2Function) {
        MethodAccess methodAccess = MethodAccess.get(clazz);
        Object obj = null;
        try {
            obj = clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            LogUtil.game.error("Exception", e);
        }
        // 获取方法上的注解
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getDeclaredAnnotations();
            Annotation annotation = annotations[0];
            if (annotation instanceof MsgFunction) {
                final int msgIndex = ((MsgFunction) annotation).value().getNumber();
                int methodIndex = methodAccess.getIndex(method.getName(), MsgPack.class);

                MsgMethod msgMethod = new MsgMethod();
                msgMethod.setObj(obj);
                msgMethod.setMethodIndex(methodIndex);
                msgMethod.setMethodAccess(methodAccess);
                msg2Function.put(msgIndex, msgMethod);
            }
        }
    }

    public void addMessagePack(MsgPack msgPack) {
        if (!this.start) {
            return;
        }
        MsgMain.MessageType messageType = msgPack.getMainProtocl().getMessageType();
        LogUtil.game.info("【接收】" + messageType.name() + ":" + msgPack.getMsgIndex() + "【channelId】" + msgPack.getChannel().id().asLongText() + "----" + msgPack.getMainProtocl().toString().replace("\n", " "));

        if (messageType == MsgMain.MessageType.CS_Login) {
            //登陆协议集中处理,单线程好控制
            this.loginScene.addMessagePack(msgPack);
        } else {
            if (null == msgPack.getToken()) {
                LogUtil.game.error("登录还未成功 token未赋值");
                return;
            }
            //登录后的协议，放到角色对应scene的MsgProcessor中
            MsgDispatcherManager.getInstance().addMessagePack(msgPack);
        }
    }
}
